package ServiceGraph

import (
	"fmt"
	"github.com/neo4j/neo4j-go-driver/v4/neo4j"
	"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
	"log"
	"time"
	"ustcLab/conf"
	"ustcLab/server/dao/bean"
)

const (
	// Cypher item type
	typeDisease = "disease" // e.g. disease cause by reason

	// Cypher item fields
	fieldDiseaseName = "name"
	fieldDiseaseDesc = "description"

	/*
		Cypher template:

		e.g.: CREATE (x:NewB { a: "food", relation: "connect", b: "apple" })
		MATCH (jennifer:Person {name: 'Jennifer'}) MATCH (mark:Person {name: 'Mark'}) CREATE (jennifer)-[rel:IS_FRIENDS_WITH]->(mark)
	*/
)

var Driver neo4j.Driver

func init() {
	var err error
	Driver, err = neo4j.NewDriver(conf.Neo4jUri, neo4j.BasicAuth(conf.Neo4jUsername, conf.Neo4jPassword, ""))
	if err != nil {
		log.Fatalln("[ServiceGraph] failed to connect neo4j database!")
		return
	}
}

// FindObject 查找主体关联的客体对象集合
func FindObject(substance, relation string) ([]*bean.TripleData, error) {
	session := Driver.NewSession(neo4j.SessionConfig{
		AccessMode: neo4j.AccessModeWrite,
	})
	defer session.Close()

	ret := make([]*bean.TripleData, 0)

	cypher := fmt.Sprintf("MATCH (x:disease{name: '%s'})-[r:`%s`]->(y) return x, y", substance, relation)
	res, err := session.Run(
		cypher,
		map[string]interface{}{},
	)
	if err != nil {
		return ret, err
	}
	for res.Next() {
		r := res.Record().Values[1].(dbtype.Node).Props["name"].(string)
		log.Println("[FindObject] find obj name: ", r)
		ret = append(ret, &bean.TripleData{
			Substance: substance,
			Relation: relation,
			Object: r,
		})
	}

	return ret, nil
}

// FindByDiseaseAndRelation 模糊搜索 By症状
func FindByDiseaseAndRelation(substance, relation string) ([]*bean.Disease, error) {

	session := Driver.NewSession(neo4j.SessionConfig{
		AccessMode: neo4j.AccessModeWrite,
	})
	defer session.Close()

	ret := make([]*bean.Disease, 0)

	cypher := fmt.Sprintf("MATCH (x:disease)-[r:`%s`]->(y) WHERE x.name=~'.*%s.*' RETURN x, y", relation, substance)
	res, err := session.Run(
		cypher,
		map[string]interface{}{},
	)
	if err != nil {
		return ret, err
	}
	for res.Next() {
		r := res.Record().Values[1].(dbtype.Node).Props["name"].(string)
		log.Println("[FindObject] find obj name: ", r)
		o := &bean.Disease{
			Name: r,
		}
		ret = append(ret, o)
	}

	return ret, nil
}

// InsertItem 插入三元组
func InsertItem(tripleData *bean.TripleData) (string, error) {
	session := Driver.NewSession(neo4j.SessionConfig{
		AccessMode: neo4j.AccessModeWrite,
	})
	defer session.Close()


	cypher := fmt.Sprintf("MATCH(x:disease) WHERE x.name='%s' return x", tripleData.Substance)
	res, err := session.Run(
		cypher,
		map[string]interface{}{},
	)
	if err != nil {
		return "", err
	}
	hasExistsSubstance, hasExistsObject := false, false
	if res.Next() {
		name := res.Record().Values[0].(dbtype.Node).Props["name"]
		if name == tripleData.Substance {
			log.Println("[InsertItem] substance name has exists!")
			hasExistsSubstance = true
		}
		if name == tripleData.Object {
			log.Println("[InsertItem] object name has exists!")
			hasExistsObject = true
		}
	}

	transaction, err := session.BeginTransaction(func(config *neo4j.TransactionConfig) {
		config.Timeout = 3 * time.Second
	})
	if !hasExistsSubstance {
		cypher = fmt.Sprintf("CREATE (x:disease { name:'%s' }) return x", tripleData.Substance)
		_, _ = transaction.Run(
			cypher,
			map[string]interface{}{})
	}
	if !hasExistsObject {
		cypher = fmt.Sprintf("CREATE (x:disease { name:'%s' }) return x", tripleData.Object)
		_, _ = transaction.Run(
			cypher,
			map[string]interface{}{})
	}

	cypher = fmt.Sprintf("MATCH (x:disease {name: '%s'}) MATCH (y:disease {name: '%s'}) CREATE (x)-[rel:%s]->(y)",
		tripleData.Substance,
		tripleData.Object,
		tripleData.Relation)
	_, _ = transaction.Run(
		cypher,
		map[string]interface{}{})

	err = transaction.Commit()
	if err != nil {
		log.Fatalln("[InsertItem] failed to commit Transaction! err:", err.Error())
		return "", err
	}
	return "", nil

}